package com.babel.util.util;

import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;

import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion;
import org.codehaus.jackson.type.TypeReference;

/**
 * @author sylorl
 * @param <T>
 */
public final class JSONBinder<T> {
	
    public static <T> JSONBinder<T> binder(Class<T> beanClass, Class<?>... elementClasses) {
        return new JSONBinder<T>(beanClass, elementClasses);
    }

    public static <T> JSONBinder<T> binder(TypeReference<?> type) {
        return new JSONBinder<T>(type);
    }
    
    public static ObjectMapper createMapper() {
        ObjectMapper mapper = new ObjectMapper();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        mapper.setSerializationConfig(mapper.getSerializationConfig().withDateFormat(dateFormat));
        mapper.setDeserializationConfig(mapper.getDeserializationConfig().withDateFormat(dateFormat));
        //mapper.setAnnotationIntrospector(new JaxbAnnotationIntrospector());
        return mapper;
    }

    private Class<T> beanClass;

    private Class<?>[] elementClasses;

    private TypeReference<?> type;
    
    private JSONBinder(Class<T> beanClass, Class<?>... elementClasses) {
        this.beanClass = beanClass;
        this.elementClasses = elementClasses;
    }

    private JSONBinder(TypeReference<?> type) {
    	this.type = type;
    }
    
    public T fromJSON(String json) {
        ObjectMapper mapper = createMapper();
        try {
        	if(type != null) {
        		return mapper.readValue(json, type);
        	} else {
        		return elementClasses == null || elementClasses.length == 0 ? mapper.readValue(json, beanClass) : fromJSONToGeneric(json);
        	}
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    
    public T fromJSON(String json, TypeReference<?> type) {
        ObjectMapper mapper = createMapper();
        try {
        	return mapper.readValue(json, type);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    
    public T fromJSON(byte[] json) {
    	ObjectMapper mapper = createMapper();
        try {
        	if(type != null) {
        		return mapper.readValue(json, type);
        	} else {
        		return elementClasses == null || elementClasses.length == 0 ? mapper.readValue(json, beanClass) : fromJSONToGeneric(json);
        	}
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public T fromJSON(InputStream json) {
        ObjectMapper mapper = createMapper();
        try {
        	if(type != null) {
        		return mapper.readValue(json, type);
        	} else {
        		return elementClasses == null || elementClasses.length == 0 ? mapper.readValue(json, beanClass) : fromJSONToGeneric(json);
        	}
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    
    private T fromJSONToGeneric(String json) throws IOException {
        ObjectMapper mapper = createMapper();
        return mapper.readValue(json, mapper.getTypeFactory().constructParametricType(beanClass, elementClasses));
    }
    
    private T fromJSONToGeneric(byte[] json) throws IOException {
        ObjectMapper mapper = createMapper();
        return mapper.readValue(json, mapper.getTypeFactory().constructParametricType(beanClass, elementClasses));
    }
    
    private T fromJSONToGeneric(InputStream json) throws IOException {
        ObjectMapper mapper = createMapper();
        return mapper.readValue(json, mapper.getTypeFactory().constructParametricType(beanClass, elementClasses));
    }
    
    public String toJSON(T object) {
        ObjectMapper mapper = createMapper();
        try {
            return mapper.writeValueAsString(object);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    
    public String toJSON(T object, ObjectMapper mapper) {
    	if(mapper == null) mapper = createMapper();
        try {
            return mapper.writeValueAsString(object);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    
    public String toJSON(T object, Inclusion inclusion) {
        ObjectMapper mapper = createMapper();
        if(inclusion == null) inclusion = Inclusion.ALWAYS;
        mapper.setSerializationInclusion(inclusion);
        try {
            return mapper.writeValueAsString(object);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    
    public byte[] toBytes(T object) {
        ObjectMapper mapper = createMapper();
        try {
            return mapper.writeValueAsBytes(object);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    
    public byte[] toBytes(T object, ObjectMapper mapper) {
    	if(mapper == null) mapper = createMapper();
        try {
            return mapper.writeValueAsBytes(object);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    
    public byte[] toBytes(T object, Inclusion inclusion) {
        ObjectMapper mapper = createMapper();
        if(inclusion == null) inclusion = Inclusion.ALWAYS;
        mapper.setSerializationInclusion(inclusion);
        try {
            return mapper.writeValueAsBytes(object);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
